zhouqijie

一、顶点属性和顶点缓冲区

概述:

想要绘制一个对象,它的顶点数据(pos,norm,color,uv…)需要被发送给顶点着色器。
通常会把顶点数据在C++端放入一个顶点缓冲区(VBO),并把顶点缓冲区和着色器中声明的顶点属性相关联。

布局:

布局1:数组的结构(SOA)

&nbsp position1 | position2 | position3 | ...
&nbsp normal1 | normal2 | normal3 | ...
&nbsp color1 | color2 | color3 | ...

布局2:结构的数组(AOS)

&nbsp vertex1 | vertex2 | vertex3 | ...


VBO注释:

VBO:顶点缓存对象。(包含顶点缓冲区)

顶点缓冲对象VBO是在显卡存储空间中开辟出的一块内存缓存区,用于存储顶点的各类属性信息,如顶点坐标,顶点法向量,顶点颜色数据等。在渲染时,可以直接从VBO中取出顶点的各类属性数据,由于VBO在显存而不是在内存中,不需要从CPU传输数据,处理效率更高。
https://blog.csdn.net/dcrmg/article/details/53556664

VAO注释:

顶点数组对象。(存储状态配置,是一种组织顶点缓冲区的方法,使之在复杂场景中更容易操控)(VAO不存储实际的数据。)

Creedon:也许使用相同的着色器的模型可以用同一个VAO。


实现步骤:

初始化Init中:

渲染display中:

二、一致变量(Uniform Var)

概述:

一致变量即外部程序传给OpenGL的变量。例如要渲染一个3D场景,需要构建MV、P变换矩阵,习惯上会将这些矩阵从C++程序传给着色器的一致变量(包括顶点着色器和片段着色器)。

声明:

一致变量用uniform关键字声明。例如uniform mat4 mv_matrix;

传递的实现:

//获取着色器中一致变量的位置
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
//将数据传到一致变量中
glUniformMaxtrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));


三、顶点属性插值

光栅化过程会线性插值顶点属性值。

注意:一致变量本身不是插值的,它始终保持不变。

常被插值的属性有法线、纹理坐标uv、颜色等。

顶点输入/输出属性声明:

layout (location = 0) in vec3 position;
out color;

内置的属性:




四、同一个模型的重复渲染

方式1: 重复调用glDrawArrays():在display()中构建差异。

方式2: 调用glDrawArraysInstanced():在着色器代码中构建差异。(OpenGL实例化机制)(差异因子由程序传入uniform变量)


五、多个模型的渲染

用多个顶点缓冲区(vbo)保存多个物体的顶点信息。

注意:

P矩阵可共用,MV矩阵要分别计算。


六、多层级模型渲染和矩阵堆栈

实际建模中通常会通过组装较小的简单模型构建复杂模型。这种方式构建的对象称为分层级模型。分层模型不仅可用于构建复杂对象,还可以用来生成复杂场景。

使用矩阵堆栈使得创建和管理复杂分层对象和场景变得容易。它使得变换可以构建在其他变换之上(或者从其他变换中被移除)。

※可以使用C++标准模板库(STL)的stack类。

堆栈结构和实现:

第一个入栈的是V矩阵。然后依次是父物体、子物体的MV矩阵。

如果在父对象上创建子对象时,使用push命令在堆栈顶部创建新的矩阵,然后再根据需要将期望的变换应用于堆栈顶部新创建的矩阵。(stack.push(stack.top())

应用新对象所需变换,也就是所需的变换乘以它。


补充:另一种VBO布局(AOS)

#define VERTEX_POS_INDX 0
#define VERTEX_POS_SIZE 3

#define VERTEX_COLOR_INDX 1
#define VERTEX_COLOR_SIZE 4

#define VERTEX_ATTRIB_SIZE (VERTEX_POS_SIZE+VERTEX_COLOR_SIZE)

GLfloat vertex[]={
            0.0f,1.0f,1.0f, //x,y,z
			1.0f,0.0f,0.0f,1.0f, //RGBA
			1.0f,0.5f,1.0f,
			0.0f,1.0f,0.0f,1.0f,
			0.0f,0.5f,1.0f,
			0.0f,0.0f,1.0f,1.0f
		 }
//设置顶点位置属性数据数组
glVertexAttribPointer(VERTEX_POS_INDX,VERTEX_POS_SIZE,GL_FLOAT,GL_FALSE,VERTEX_ATTRIB_SIZE*sizeof(GLfloat),vertex);
//设置颜色属性数据数组
glVertexAttribPointer(VERTEX_COLOR_INDX,VERTEX_COLOR_SIZE,GL_FLOAT,GL_FALSE,(VERTEX_ATTRIB_SIZE*sizeof(GLfloat),vertex+VERTEX_POS_SIZE));
//启用顶点数组数据
glEableVertexAttribArray(VERTEX_POS_INDE);
glEnableVertexAttribArray(VERTEX_COLOR_INDE);
//根据顶点数据绘制三角形
glDrawArrays(GL_TRIANGLES,0,3);
//禁用顶点数组数据
glDisableVertexAttribArray(VERTEX_POS_INDE);
glDisableVertexAttribArray(VERTEX_COLOR_INDE);

关于glVertexAttribPointer()的参数:

补充:使用索引绘制(glDrawElements())

使用glDrawElements()glDrawArrays()相比:

参数相关:

注意事项:


setVertexBuffers():

//使用`glDrawElements()`则需要额外定义一个数组缓冲保存索引。
vector<int> ind = model.getIndicates();
//...
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, ind.size() * 4, &ind[0], GL_STATIC_DRAW);

display():

//...//把索引缓冲区激活后开始draw elements.
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
glDrawElements(GL_TRIANGLES, model.getNumIndicates(), GL_UNISIGNED_INT, 0);


补充:使用glBegin()/glEnd()绘制

用于固定管线。已被弃用。

补充:深度缓存和深度测试

OpenGL使用深度测试来进行隐藏面消除。使用glEnable(GL_DEPTH_TEST)glDepthFunc(GL_LEQUAL/GL_LESS)开启深度测试并指定判断函数(小于等于或者小于时通过)。

在渲染帧之前要用glClear(GL_DEPTH_BUFFER_BIT)清除深度缓冲区,它使用默认值1.0来填充。默认情况下,OpenGL深度值为0.0~1.0。

int display(...)
{
//...
glClear(GL_DEPTH_BUFFER_BIT);//清除深度缓冲
//...
glEnable(GL_DEPTH_TEST);//开启深度测试
glDepthFunc(GL_LEQUAL);//深度Func
}
//draw...


补充:”Z冲突”走样问题

当两个物体表面重叠时,这使得Z-Buffer算法难以确定应该渲染两个表面中的哪一个。此时,浮点误差可能会导致渲染出不自然的走样。

解决方案:

  1. 稍微移动一个物体避免重叠。
  2. 适当减少近裁剪面和远裁剪面之间的距离。(特别是近裁剪面不宜太近)


补充:性能优化

  1. 尽量减少内存分配:
    不要在display()中声明变量或者实例化对象。

  2. 预先计算透视矩阵:
    把透视矩阵的计算放在init()函数中,只计算一次。
    (如果改变了窗口,可以在调用init()之前使用glfwSetWindowSizeCallback(window, callback)设置回调)

  3. 背面剔除:
    使用glEnable(GL_CULL_FACE)/glDisable(GL_CULL_FACE)启用/禁用背面剔除。
    使用glCullFace(GL_BACK/GL_FRONT/GL_GRONT_AND_BACK)设置剔除背面/正面/全部剔除。
    使用glFrontFace(GL_CCW/GL_CW)显式设置3个顶点逆时针(默认)/顺时针排列时为正向。